﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Weazel.Badger.Vhdl.Types
{
  public class BinaryTypeResolver
  {
    private bool resultIsValid;

    private BinaryTypeMap[] maps;

    private ITypedComponent left;
    private ITypedComponent right;
    private ITypedComponent parent;

    private Dictionary<TypeId, BinaryConversionResult> results;

    #region Properties
    public BinaryTypeMap[] Maps
    {
      get
      {
        return maps;
      }

      set
      {
        maps = value;
        resultIsValid = false;
      }
    }


    public ITypedComponent Left
    {
      get
      {
        return left;
      }

      set
      {
        left = value;
        resultIsValid = false;
      }
    }

    public ITypedComponent Right
    {
      get
      {
        return right;
      }

      set
      {        
        right = value;
        resultIsValid = false;
      }
    }
    #endregion

    public BinaryTypeResolver(
      ITypedComponent left,
      ITypedComponent right,
      ITypedComponent parent,
      BinaryTypeMap[] maps
    )
    {
      this.left = left;
      this.right = right;
      this.parent = parent;
      this.maps = maps;

      this.results = new Dictionary<TypeId, BinaryConversionResult>();


      this.resultIsValid = false;
    }

    public BinaryConversionResult GetConversions(TypeId type)
    {
      if (!resultIsValid)
        throw new Exception("Result is not valid. Must call 'ComputePossibleResultTypes'");

      if (!results.ContainsKey(type))
        throw new Exception("Specified type is not present in results set");

      return results[type];
    }

    private TypeSet createTypeSetResult()
    {
      TypeSet result = new TypeSet();

      foreach (TypeId type in results.Keys)
      {
        if (result.Contains(type))
          throw new Exception("Type already present in set");

        result.Add(Types.CreateType(type, parent.Type.Width, false));
      }

      return result;
    }

    public TypeSet ComputePossibleResultTypes()
    {
      if (resultIsValid)
        return createTypeSetResult();

      results = new Dictionary<TypeId, BinaryConversionResult>();

      foreach (BinaryTypeMap map in maps)
      {
        TypeConversionResult lr = 
          TypeConverter.GetConversion(
            left.GetPossibleTypes(), 
            Types.CreateType(
              map.Left, 
              parent.Type.Width, //left.Type.Width,
              map.IgnoreWidthDifference
            ),
            left.GezelType,   
            parent.GezelType,       
            map.IgnoreWidthDifference
          );

        TypeConversionResult rr = 
          TypeConverter.GetConversion(
            right.GetPossibleTypes(), 
            Types.CreateType(
              map.Right,
              parent.Type.Width,
             // right.Type.Width,
              map.IgnoreWidthDifference
            ),
            right.GezelType,
            parent.GezelType,            
            map.IgnoreWidthDifference
          );

        // lr = null or rr = null -> no conversion possible
        if (lr != null && rr != null)
        {
          if (results.ContainsKey(map.Result))
          {
            // target type already in result collection. Replace if this conversion 
            // is better (requires fewer conversions).
            BinaryConversionResult r = results[map.Result];

            if (r.LeftConversions.Depth + r.RightConversions.Depth > lr.Depth + rr.Depth)
            {
              r.LeftConversions = lr;
              r.RightConversions = rr;
              r.Map = map;
            }
          }
          else
          {
            // not in result collection? add it
            results.Add(map.Result, new BinaryConversionResult(lr, rr, map));
          }
        }
      }

      if (results.Count == 0)
      {
        string msg = String.Format("Cannot convert from type {0} to {1}", left.GetPossibleTypes(), right.GetPossibleTypes()); // fromGezelType, toGezelType);

        throw new Exception(msg);
      }

      resultIsValid = true;

      // finally from dictionary to TypeSet. Check for duplicates.
      return createTypeSetResult();
    }
  }
}
